#!usr/bin/env python
# -*- coding:utf-8 _*-
"""
@author:zhengxin
@file: 03_closure.py
@time: 2023/12/5  14:45
# @describe: Python闭包概念入门
"""
'''
    Python闭包概念入门
    闭包（Closure）是 Python 中一个重要的工具。
        闭包：高阶函数中，内层函数携带外层函数中的参数、变量及其环境，一同存在的状态(即使已经离开了创造它的外层函数)，被称之为闭包。
            被携带的外层变量称之为：自由变量，也被形容为：外层变量被闭包捕获了。
            闭包中的自由变量有两个神奇的特性：
                1. 第一个特性是，自由变量在闭包存在的期间，其中的值也会一直存在。因此闭包可以持有状态。
                2. 另一个特性是，闭包与闭包之间的状态是隔离的。

        闭包的几个特性：
        它是一个嵌套函数
        它可以访问外部作用域中的自由变量
        它从外层函数返回
 '''


# 在 Python 中，函数可以根据给定的参数返回一个值：
def hello(name):
    return "hello " + name


print(hello("Bob"))

# 与 Python 的其他对象（如字符串、整数、列表等）一样，函数也是对象，也可以赋值给一个变量
h = hello
print(hello)  # 输出： <function hello at 0x108e903a0>

print(h)  # 输出： <function hello at 0x108e903a0>

# 可以看到 hello 和 h 都指向同一个函数，而函数后加括号 h('Jack') 是对其进行了调用。
print(h('jack'))  # 输出： hello jack

''' 1.函数里的函数 '''


def hi():
    def bob():
        return 'Bob'

    print('Hi ' + bob())


# 输出： Hi Bob
hi()
# 此时的 bob 函数的作用域在 hi 之内的。如果在全局调用 bob() 会引发错误：
# bob()


''' 
2.函数作为返回值
    函数可以作为返回值，也可以内部定义。这种在函数里传递、嵌套、返回其他函数的情况，称之为高阶函数。
    函数还可以作为其他函数的参数。
    闭包：高阶函数中，内层函数携带外层函数中的参数、变量及其环境，一同存在的状态(即使已经离开了创造它的外层函数)，被称之为闭包。
        被携带的外层变量称之为：自由变量，也被形容为：外层变量被闭包捕获了。
'''


def cook():
    def tomato():
        print('I am Tomato')

    return tomato


t = cook()
t()  # 输出： I am Tomato

'''
3. 闭包与自由变量:
    通常来说，函数中的变量为局部变量，一但函数执行完毕，其中的变量就不可用了：

'''


def cook():
    food = 'apple'


cook()
# print(food)  # 输出报错：NameError: name 'food' is not defined


'''
同样的情况到了高阶函数这里，就有点不对劲了:
    cook() 函数执行之后，按道理来说 food 变量就应该被销毁掉了。但实际上没有任何报错， value() 顺利的输出了 food 的值。
    甚至于，即使将 cook() 函数销毁了，food 的值都还存在：

'''


def cook():
    food = 'apple'

    def wrapper():
        print(food)

    return wrapper


value = cook()
value()  # 输出:apple

# 删除原函数
del cook

value()  # 输出:apple

'''
4. 参数捕获：
    很多时候，我们希望闭包所捕获的自由变量可以根据不同的情况有所区分。
    很简单，把它作为外层函数的参数就可以了：
'''


def cook(name):
    def wrapper():
        print('I am cooking ' + name)

    return wrapper


apple = cook('apple')
pear = cook('pear')
# 外层函数的参数也可以成为自由变量，被封装到内层函数所在的环境中
# 这种局部变量起作用的特定环境，有时候被称为作用域或者域。
apple()  # 输出： I am cooking apple
pear()  # 输出： I am cooking pear

'''
5. 函数生成：
    既然外层函数可以携带参数，那被返回的内层函数当然也可以带参数：

'''


def outer(x):
    def inner(y):
        print(x + y)

    return inner


# 看到两个括号就代表进行了两次函数调用。第一个括号对应 outer 的参数 x ，第二个括号里对应 inner 的参数 y。
outer(1)(2)  # 输出： 3


# 利用闭包携带参数并返回函数的这个特性，可以很方便的在一个底层的函数框架上，组装出不同的功能
# 外层函数传递的参数甚至可以是个函数。
def add(x):
    def inner(y):
        print(x + y)

    return inner


add_one = add(1)
add_ten = add(10)

add_one(5)  # 输出： 6
add_ten(5)  # 输出： 15

'''
6. 状态持有:
    闭包中的自由变量有两个神奇的特性:
        第一个特性是，自由变量在闭包存在的期间，其中的值也会一直存在。因此闭包可以持有状态。
        另一个特性是，闭包与闭包之间的状态是隔离的。

    以上两个特性，使得闭包像一个微型的类，因为状态持有和数据隐藏是类的基本功能。
    它两在使用上的建议是：如果你的状态比较简单，那么可以用闭包来实现；相反则使用类。
'''


# 记录每次取得的分数- score 闭包打印的列表记录了每次调用的结果。
def make_score():
    lis = []

    def inner(x):
        lis.append(x)
        print(lis)

    return inner


score = make_score()
score(60)  # 输出：[60]
score(77)  # 输出：[60, 77]
score(88)  # 输出：[60, 77, 88]


# 另一个特性是，闭包与闭包之间的状态是隔离的。
def make_score():
    lis = []

    def inner(x):
        lis.append(x)
        print(lis)

    return inner


first = make_score()
second = make_score()

first(1)  # 输出：[1]
first(2)  # 输出：[1, 2]

second(3)  # 输出：[3]
second(4)  # 输出：[3, 4]

'''
7. 不变量状态:
    在上面的例子里，闭包用 lis.append() 直接操作了自由变量。
    但如果要操作的自由变量是个不变量，比如数值型、字符串等，那么记得加 nonlocal 关键字：
        此关键字就是在告诉解释器：接下来的 total 不是本函数里的局部变量，你最好去闭包或是别的地方找找。

'''


# 记录成绩总分
def make_score():
    total = 10

    def inner(x):
        nonlocal total
        total += x
        print(total)

    return inner


total = make_score()
total(5)  # 输出：15

'''
8. 延迟陷阱：
    闭包相关的常见陷阱
    inner 是延迟执行的，直到真正调用前，都是没进行内部操作的。
'''
funcs = []
for i in range(3):
    def inner():
        print(i)


    funcs.append(inner)

# print(f'i is: {i}')
funcs[0]()  # 输出：2
funcs[1]()  # 输出：2
funcs[2]()  # 输出：2

# 解决方案就是用闭包将 i 的值立即捕获：
funcs = []
for i in range(3):
    def outer(a):
        def inner():
            print(a)

        return inner


    funcs.append(outer(i))

print(f'i is: {i}')
funcs[0]()  # 输出：0
funcs[1]()  # 输出：1
funcs[2]()  # 输出：2

"""
9. 组合函数：
    用闭包实现函数的拼接
"""


def compose(g, f):
    def inner(*args, **kwargs):
        return g(f(*args, **kwargs))

    return inner


# 被拼接函数1
def remove_lase(lis):
    return lis[1:]


# 被拼接函数2
def remove_last(lis):
    return lis[:-1]


# 进行函数合成
remove_last_remove_last = compose(remove_last, remove_lase)
new_list = remove_last_remove_last([1, 2, 3, 4, 5])
print(new_list)  # 输出：[2, 3, 4]

"""
10. 柯里化
    柯里化是闭包的一种应用，它将一个多参数的函数转换成一系列单参数函数。

"""


# 柯里化闭包函数
def curry(f):
    argc = f.__code__.co_argcount
    f_args = []
    f_kwargs = {}

    def g(*args, **kwargs):
        nonlocal f_args, f_kwargs
        f_args += args
        f_kwargs.update(kwargs)
        if len(f_args) + len(f_kwargs) == argc:
            return f(*f_args, **f_kwargs)
        else:
            return g

    return g


# 无关紧要的原函数
def add(a, b, c):
    return a + b + c


# c_add 是被柯里化的新函数
c_add = curry(add)




